/*
 * @Author: Jiraiya
 * @Date: 2020-03-10 19:09:17
 * @LastEditors: Jiraiya
 * @LastEditTime: 2020-03-11 14:04:46
 * @Description: 
 */

const isFunction = variable => typeof variable === 'function'
const [PENDING, FULFILLED, REJECTED] = ['pending', 'fulfilled', 'rejected']

class Promise{
    constructor(handle){
        
        if(!isFunction(handle)){
            throw new Error('Promise must accept a function as a parameter')
        }

        this._status = PENDING
        this._value = undefined
        this._fulfilledQueues = []
        this._rejectedQueues = []

        try{
            handle(this._resolve.bind(this), this._reject.bind(this))
        }catch(err){
            this._reject(err)
        }
    }

    _resolve(val){ 
        const run = () => {
            if(this._status !== PENDING) return

            const runFulfilled = value => {
                let cb;
                while(cb = this._fulfilledQueues.shift()){
                    cb(value)
                }
            }

            const runRejected = error => {
                let cb;
                while(cb = this._rejectedQueues.shift()){
                    cb(error)
                }
            }

            if(val instanceof Promise){
                val.then(value => {
                    this._value = value
                    this._status = FULFILLED
                    runFulfilled(value)
                }, err => {
                    this._value = err
                    this._status = REJECTED
                    runRejected(err)
                })
            }else{
                this._value = val
                this._status = FULFILLED
                runFulfilled(val)
            }

        }

        setTimeout(() => run(), 0)
        
    }

    _reject(err){
        if(this._status !== PENDING) return
        const run = () => {
            this._status = REJECTED
            this._value = err
            let cb;
            while(cb = this._rejectedQueues.shift()){
                cb(err)
            }
        }

        setTimeout(() => run(), 0)
    }

    then(onFulfilled, onRejected){
        const {_status, _value, _fulfilledQueues, _rejectedQueues} = this

        return new Promise((onFulfilledNext, onRejectedNext) => {
            let fulfilled = _value => {
                try{
                    if(!isFunction(onFulfilled)){
                        onFulfilledNext(_value)
                    }else{
                        let res = onFulfilled(_value)
                        if(res instanceof Promise){
                            res.then(onFulfilledNext, onRejectedNext)
                        }else{
                            onFulfilledNext(_value)
                        }
                    }
                }catch(err){
                    onRejectedNext(err)
                }
            }

            let rejected = err => {
                try{
                    if(!isFunction(onRejected)){
                        onRejectedNext(err)
                    }else{
                        let res = onRejected(err)
                        if(res instanceof Promise){
                            res.then(onFulfilledNext, onRejectedNext)
                        }else{
                            onFulfilledNext(res)
                        }
                    }
                }catch(err){
                    onRejectedNext(err)
                }
            }

            switch(_status){
                case PENDING:
                    _fulfilledQueues.push(fulfilled)
                    _rejectedQueues.push(rejected)
                    break
                case FULFILLED:
                    fulfilled(_value)
                    break
                case REJECTED:
                    rejected(_value)
                    break
            }

            
        })
    }

    catch (onRejected){
        return this.then(undefined, onRejected)
    }

    finally(cb){
        return this.then(
            val => Promise.resolve(cb()).then(() => val),
            reason => Promise.resolve(cb()).then(() => {throw reason})
        )
    }

    static resolve(val){
        if(val instanceof Promise) return val
        return new Promise(resolve => resolve(val))
    }

    static reject(val){
        return new Promise((resolve, reject) => reject(val))
    }

    static all(list){
        return new Promise((resolve, reject) => {
            let values = [], count = 0
            for(let [i, p] in list.entries()){
                this.resolve(p).then(res => {
                    values[i] = res
                    count++
                    if(count === list.length) resolve(values)
                }, err => {
                    reject(err)
                })
            }
        })
    }

    static race(list){
        return new Promise((resolve, reject) => {
            [...list].forEach(p => {
                this.resolve(p).then(res => {
                    resolve(res)
                }, err => {
                    reject(err)
                })
            })
        })
    }

    static deferred() { // 延迟对象
        let defer = {};
        defer.promise = new Promise((resolve, reject) => {
            defer.resolve = resolve;
            defer.reject = reject;
        });
        return defer;
    }
    
}



/**
 * Promise/A+规范测试
 * npm i -g promises-aplus-tests
 * promises-aplus-tests Promise.js
 */
module.exports = Promise
// new Promise((re, rj)=>{
//     setTimeout(()=>{
//         re(1)
//     },1000)
// }).then(re=>{
//     console.log(re)
// })